<?php
/**
 * @file WCSearch.php
 * @author Alejandro Dario Simi
 * @date $Date: 2013-03-30 18:08:43 +0000 (Sat, 30 Mar 2013) $
 *
 * $Id: WCSearch.php 59 2013-03-30 18:08:43Z daemonraco@gmail.com $
 * $URL: http://wcomix.googlecode.com/svn/tags/wcomix-1.0-BETA1/includes/search/WCSearch.php $
 */

require_once dirname(__FILE__)."/WCSearchFactory.php";
require_once dirname(__FILE__)."/WCSearchContainer.php";

/**
 * @interface WCSearchIndexable
 */
interface WCSearchIndexable {
	public function id();
	public function indexed($value=null);
	public function words();
}

/**
 * @class WCSearch
 */
class WCSearch {
	public static	$CacheId	= "SEARCH";

	/**
	 * @var WCSearch
	 */
	protected static	$_Instance;

	/**
	 * @var WCConnection
	 */
	protected	$_db;
	/**
	 * @var string
	 */
	protected	$_dbprefix;

	protected	$_stmtLoaded	= false;
	protected	$_stmtId;
	/**
	 * @var PDOStatement
	 */
	protected	$_stmtIndexSelect;
	/**
	 * @var PDOStatement
	 */
	protected	$_stmtIndexInsert;
	/**
	 * @var PDOStatement
	 */
	protected	$_stmtItemSelect;
	/**
	 * @var PDOStatement
	 */
	protected	$_stmtIndexLike;
	/**
	 * @var array
	 */
	protected	$_loadedWords	= array();
	/**
	 * @var array
	*/
	protected	$_times		= array();
	/**
	 * @var WCTimer
	*/
	protected	$_timer		= null;

	/**
	 * @var WCSearchContainer
	 */
	protected	$_results	= null;

	protected function __construct() {
		global	$wcDatabaseAccess;
		global	$wcDefaults;

		$this->_db		= WCConnection::Instance();
		$this->_dbprefix	= $wcDatabaseAccess['prefix'];
		$this->_stmtId		= basename(__FILE__);

		$this->_timer	= WCTimer::Instance();
	}
	public function __destruct() {
	}
	/**
	 * Prevent users to clone the instance.
	 */
	public function __clone() {
		trigger_error(__CLASS__.": Clone is not allowed.", E_USER_ERROR);
	}

	//
	// Public methods
	public function buildTime() {
		return $this->timer("build");
	}
	public function elapsedTime() {
		return $this->timer("elapsed");
	}
	public function filteringTime() {
		return $this->timer("filter");
	}
	public function get(&$words=array(), WCGroup &$group, $stats=true, &$onlyTypes=array(), &$execptions=array()) {
		$this->startTimer("elapsed");

		global	$wcPageCache;

		$words	= array_keys(array_count_values($words));
		$code	= base64_encode("[{$group->id}][".implode("|", $words)."][".implode("|", $execptions)."]");

		$results = new WCSearchContainer();
		$eResults = null;
		$results->setWords($words);
		if($execptions) {
			$eResults = new WCSearchContainer();
			$eResults->setWords($words);
		}

		$this->startTimer("seek");
		if(!$wcPageCache->get(self::$CacheId, false, $code)) {
			$this->loadStatements();

			$this->getItems($results, $group, $words, $this->_stmtIndexSelect, true);
			$this->getItems($results, $group, $words, $this->_stmtIndexLike, false);
			$this->secondLayer($results);

			if($execptions) {
				$this->startTimer("filter");
				$this->getItems($eResults, $group, $execptions, $this->_stmtIndexSelect, true);
				$this->getItems($eResults, $group, $execptions, $this->_stmtIndexLike, false);
				$this->secondLayer($eResults);
				$results->filter($eResults);
				$this->stopTimer("filter");
			}
				
			$result	= $results->items();

			$wcPageCache->append(self::$CacheId, serialize($result));

			$anyStatus = $wcPageCache->anyStatus();
			$wcPageCache->anyCollection();
			$wcPageCache->anyComic     ();
			$wcPageCache->anyDirectory ();
			$wcPageCache->anyTag       ();

			$wcPageCache->save(self::$CacheId, $code);

			$wcPageCache->anyCollection($anyStatus["collection"]);
			$wcPageCache->anyComic     ($anyStatus["comic"]);
			$wcPageCache->anyDirectory ($anyStatus["directory"]);
			$wcPageCache->anyTag       ($anyStatus["tag"]);
		} else {
			$result = unserialize($wcPageCache->get(self::$CacheId, false, $code));
			if($stats) {
				$lastInfo = $wcPageCache->lastInfo();

				echo "<!--\n";
				echo "This is a cached search saved under code '".self::$CacheId."[{$code}]'\n\n";
				echo "ID:   {$lastInfo["id"]}\n";
				echo "CODE: {$lastInfo["code"]}\n\n";
				echo "COMPRESSED:   {$lastInfo["compressed"]}\n";
				echo "UNCOMPRESSED: {$lastInfo["uncompressed"]}\n";
				echo "RATE:         {$lastInfo["rate"]}%\n";
				echo "-->\n";
			}
		}
		$this->stopTimer("seek");

		$this->startTimer("build");
		$items		= array();
		$checkTypes	= count($onlyTypes) > 0;
		foreach($result as $hash => $info) {
			if($checkTypes && !in_array($info["type"], $onlyTypes)) {
				continue;
			}
			$item = WCSearchFactory::BuildItem($group, $info, $hash);
			if($item->isBroken()) {
				continue;
			}
			$items[] = $item;
		}

		$this->stopTimer("build");

		$this->stopTimer("elapsed");

		return $items;
	}
	public function searchTime() {
		return $this->timer("seek");
	}
	public function secondLayerTime() {
		return $this->timer("second-layer");
	}
	public function timer($code="elapsed") {
		return $this->_timer->timer($code);
	}
	public function updateIndex($showInfo=false, $spacer="") {
		global	$wcPageCache;
		$wcPageCache->resetFullId(self::$CacheId);

		$this->loadWords();

		$this->clearZombies($showInfo, $spacer);

		$this->updateComicsIndex($showInfo, $spacer);
		$this->updateDirectoriesIndex($showInfo, $spacer);
		$this->updateTagsIndex($showInfo, $spacer);
		$this->updateCollectionsIndex($showInfo, $spacer);
		$this->updateRulesIndex($showInfo, $spacer);
	}

	//
	// Protected methods
	protected function calculateFactors(&$result) {
		foreach($result["items"] as $id => $item) {
			foreach($result["length-factor"] as $length => $factor) {
				$result["items"][$id]["factor"] += $item["lhits"][$length] * $factor;
			}
		}
	}
	protected function clearZombies($showInfo, $spacer="") {
		$stSubID = __CLASS__."::".__FUNCTION__;
		if($showInfo) {
			echo "{$spacer}Clearing Zombies:\n";
		}

		$query = "delete from  wc_search_items\n";
		$query.= "where        (sit_type = 'CX' and sit_comic      not in (select cmc_id from wc_comics where cmc_lost = 'N'))\n";
		$query.= " or          (sit_type = 'CL' and sit_collection not in (select col_id from wc_collections))\n";
		$query.= " or          (sit_type = 'TA' and sit_tag        not in (select tag_id from wc_tags))\n";
		$query.= " or          (sit_type = 'DI' and sit_directory  not in (select dir_id from wc_directories))\n";
		$stmt = $this->_db->prepare("{$stSubID}lostitems", $query);
		$stmt->execute();
		if($showInfo) {
			echo "{$spacer}\tNon existing items: {$stmt->rowCount()}\n";
		}

		$query = "delete from wc_search_index\n";
		$query.= "where        six_id not in (\n";
		$query.= "                     select  sit_word\n";
		$query.= "                     from    wc_search_items)\n";
		$stmt = $this->_db->prepare("{$stSubID}emptywords", $query);
		$stmt->execute();
		if($showInfo) {
			echo "{$spacer}\tWords without items: {$stmt->rowCount()}\n";
		}

		$query = "delete from wc_search_items\n";
		$query.= "where        sit_word not in (\n";
		$query.= "                     select  six_id\n";
		$query.= "                     from    wc_search_index)\n";
		$stmt = $this->_db->prepare("{$stSubID}lostwords", $query);
		$stmt->execute();
		if($showInfo) {
			echo "{$spacer}\tItems of no word: {$stmt->rowCount()}\n";
		}
	}
	protected function getItems(WCSearchContainer &$results, WCGroup &$group, &$words, &$stmt, $exact=false) {
		$length = count($words);

		foreach($words as $word) {
			$stmt->execute(array(":word" => $word));

			foreach($stmt->fetchAll() as $row) {
				$this->_stmtItemSelect->execute(array(
						":word"		=> $row["six_id"],
						":group"	=> $group->id
				));

				foreach($this->_stmtItemSelect->fetchAll() as $row2) {
					switch($row2["sit_type"]) {
						case "CL":
							$results->addCollection($row2["sit_collection"], $group->id, $row["six_word"], $exact);
							break;
						case "CX":
							$results->addComic($row2["sit_comic"], $group->id, $row["six_word"], $exact);
							break;
						case "DI":
							$results->addDirectory($row2["sit_directory"], $group->id, $row["six_word"], $exact);
							break;
						case "RL":
							$results->addRule($row2["sit_rule"], $group->id, $row["six_word"], $exact);
							break;
						case "TA":
							$results->addTag($row2["sit_tag"], $group->id, $row["six_word"], $exact);
							break;
					}
				}
			}
		}
	}
	protected function loadStatements() {
		if(!$this->_stmtLoaded) {
			$stSubID = __CLASS__."::".__FUNCTION__;

			$query = "select  *\n";
			$query.= "from    {$this->_dbprefix}search_index\n";
			$query.= "where   six_word = :word\n";
			$this->_stmtIndexSelect = $this->_db->prepare("{$stSubID}idxsel", $query);

			$query = "select  *\n";
			$query.= "from    {$this->_dbprefix}search_index\n";
			$query.= "where   six_word like concat(concat('%',:word),'%')\n";
			$query.= " and    six_word <> :word\n";
			$this->_stmtIndexLike = $this->_db->prepare("{$stSubID}idxlik", $query);

			$query = "insert ignore\n";
			$query.= "        into {$this->_dbprefix}search_index (six_word)\n";
			$query.= "        values (:word)\n";
			$this->_stmtIndexInsert = $this->_db->prepare("{$stSubID}idxins", $query);

			$query = "select  *\n";
			$query.= "from    {$this->_dbprefix}search_items\n";
			$query.= "where   sit_word  = :word\n";
			$query.= " and    sit_group = :group\n";
			$this->_stmtItemSelect = $this->_db->prepare("{$stSubID}itmsel", $query);

			$this->_stmtLoaded = true;
		}
	}
	protected function loadItemWords($groupId, $type, $id) {
		$out = array();

		$stmt	= null;
		$stmtId	= __CLASS__."::".__FUNCTION__."[{$type}]";
		if($this->_db->has($stmtId)){
			$stmt = $this->_db->get($stmtId);
		} else {
			$column = "";
			switch($type) {
				case "CL":
					$column = "sit_collection";
					break;
				case "CX":
					$column = "sit_comic";
					break;
				case "DI":
					$column = "sit_directory";
					break;
				case "RL":
					$column = "sit_rule";
					break;
				case "TA":
					$column = "sit_tag";
					break;
			}

			$query = "select  distinct six_word\n";
			$query.= "from    {$this->_dbprefix}search_items\n";
			$query.= "                inner join {$this->_dbprefix}search_index\n";
			$query.= "                        on (sit_word = six_id)\n";
			$query.= "where   sit_group = :group\n";
			$query.= " and    sit_type  = '{$type}'\n";
			$query.= " and    {$column} = :id\n";
			$query.= "order by six_word asc\n";

			$stmt = $this->_db->prepare($stmtId, $query);
		}

		$params = array(
				":group"	=> $groupId,
				":id"		=> $id
		);
		if($stmt->execute($params)) {
			foreach($stmt->fetchAll() as $row) {
				$out[] = $row["six_word"];
			}
		}

		return $out;
	}
	protected function loadWords() {
		$this->_loadedWords = array();

		$query = "select  *\n";
		$query.= "from    {$this->_dbprefix}search_index\n";
		$stmt = $this->_db->prepare(__CLASS__."::".__FUNCTION__, $query);

		$stmt->execute();
		foreach($stmt->fetchAll() as $word) {
			$this->_loadedWords[$word["six_word"]] = $word["six_id"];
		}
	}
	protected function secondLayer(WCSearchContainer &$results) {
		$this->startTimer("second-layer");
		$secondLayer = array();

		//
		// Even if there were no results of one of these types, the
		// empty list is built to avoid dynaic creation and gain
		// performance in the amount of instruccions and array accesses.
		foreach(WCSearchFactory::$TypePriority as $type => $value) {
			$secondLayer[$type] = array();
		}

		$entries	= array();
		$gid		= 0;
		//
		// Getting lists of ids separated by type.
		foreach(WCSearchFactory::$TypePriority as $type => $value) {
			$secondLayer[$type] = $results->typeIds($type);
		}
		$gid = $results->gid();

		//
		// Searching for second-layer tag items.
		if(isset($secondLayer["TA"][0])) {
			$stmt	= null;
			$stmtId	= __CLASS__."::".__FUNCTION__."[TA]";
			if($this->_db->has($stmtId)){
				$stmt = $this->_db->get($stmtId);
			} else {
				$query = "select  *\n";
				$query.= "from    {$this->_dbprefix}tag_members\n";
				$query.= "where   mem_group = :group\n";
				$query.= " and    mem_tag   = :tag\n";

				$stmt = $this->_db->prepare($stmtId, $query);
			}

			$params = array(
					":group"	=> $gid,
					":tag"		=> 0
			);
			foreach($secondLayer["TA"] as $tag) {
				$params[":tag"] = $tag;
				if($stmt->execute($params)) {
					$parent = $results->item("TA", $tag, $gid);

					foreach($stmt->fetchAll() as $row) {
						$results->addComic($row["mem_comic"], $gid, false, false, $parent);
					}
				}
			}
		}
		//
		// Searching for second-layer directory items.
		if(isset($secondLayer["DI"][0])) {
			$stmt	= null;
			$stmtId	= __CLASS__."::".__FUNCTION__."[DI]";
			if($this->_db->has($stmtId)){
				$stmt = $this->_db->get($stmtId);
			} else {
				$query = "select  *\n";
				$query.= "from    {$this->_dbprefix}comics\n";
				$query.= "where   cmc_group     = :group\n";
				$query.= " and    cmc_directory = :directory\n";

				$stmt = $this->_db->prepare($stmtId, $query);
			}

			$params = array(
					":group"	=> $gid,
					":directory"	=> 0
			);
			foreach($secondLayer["DI"] as $directory) {
				$params[":directory"] = $directory;
				if($stmt->execute($params)) {
					$parent = $results->item("DI", $directory, $gid);

					foreach($stmt->fetchAll() as $row) {
						$results->addComic($row["cmc_id"], $gid, false, false, $parent);
					}
				}
			}
		}
		//
		// Searching for second-layer directory items.
		if(isset($secondLayer["CL"][0])) {
			//
			// Searching for comics.
			$stmt	= null;
			$stmtId	= __CLASS__."::".__FUNCTION__."[CL+CX]";
			if($this->_db->has($stmtId)){
				$stmt = $this->_db->get($stmtId);
			} else {
				$query = "select  *\n";
				$query.= "from    {$this->_dbprefix}comics\n";
				$query.= "where   cmc_group      = :group\n";
				$query.= " and    cmc_collection = :collection\n";

				$stmt = $this->_db->prepare($stmtId, $query);
			}

			$params = array(
					":group"	=> $gid,
					":collection"	=> 0
			);
			foreach($secondLayer["CL"] as $collection) {
				$params[":collection"] = $collection;
				if($stmt->execute($params)) {
					$parent = $results->item("CL", $collection, $gid);

					foreach($stmt->fetchAll() as $row) {
						$results->addComic($row["cmc_id"], $gid, false, false, $parent);
					}
				}
			}
			//
			// Searching for collections.
			$stmt	= null;
			$stmtId	= __CLASS__."::".__FUNCTION__."[CL+CL]";
			if($this->_db->has($stmtId)){
				$stmt = $this->_db->get($stmtId);
			} else {
				$query = "select  *\n";
				$query.= "from    {$this->_dbprefix}collections\n";
				$query.= "where   col_group  = :group\n";
				$query.= " and    col_parent = :collection\n";

				$stmt = $this->_db->prepare($stmtId, $query);
			}

			$params = array(
					":group"	=> $gid,
					":collection"	=> 0
			);
			foreach($secondLayer["CL"] as $collection) {
				$params[":collection"] = $collection;
				if($stmt->execute($params)) {
					$parent = $results->item("CL", $collection, $gid);

					foreach($stmt->fetchAll() as $row) {
						$results->addCollection($row["col_id"], $gid, false, false, $parent);
					}
				}
			}
		}
		//
		// Searching for second-layer comic items.
		if(isset($secondLayer["CX"][0])) {
			//
			// Searching for comic directories and collections.
			$stmt	= null;
			$stmtId	= __CLASS__."::".__FUNCTION__."[CX]";
			if($this->_db->has($stmtId)){
				$stmt = $this->_db->get($stmtId);
			} else {
				$query = "select  *\n";
				$query.= "from    {$this->_dbprefix}comics\n";
				$query.= "where   cmc_group = :group\n";
				$query.= " and    cmc_id    = :comic\n";

				$stmt = $this->_db->prepare($stmtId, $query);
			}

			$params = array(
					":group"	=> $gid,
					":comic"	=> 0
			);
			foreach($secondLayer["CX"] as $comic) {
				$params[":comic"] = $comic;
				if($stmt->execute($params)) {
					$parent = $results->item("CX", $comic, $gid);

					foreach($stmt->fetchAll() as $row) {
						//
						// Adding comic directory.
						$results->addDirectory($row["cmc_directory"], $gid, false, false, $parent);

						//
						// Adding comic collections, when it has one.
						if($row["cmc_collection"]) {
							$results->addCollection($row["cmc_collection"], $gid, false, false, $parent);
						}
					}
				}
			}
			//
			// Searching for comic tags.
			$stmt	= null;
			$stmtId	= __CLASS__."::".__FUNCTION__."[CX+TA]";
			if($this->_db->has($stmtId)){
				$stmt = $this->_db->get($stmtId);
			} else {
				$query = "select  *\n";
				$query.= "from    {$this->_dbprefix}tag_members\n";
				$query.= "where   mem_group = :group\n";
				$query.= " and    mem_comic = :comic\n";

				$stmt = $this->_db->prepare($stmtId, $query);
			}

			$params = array(
					":group"	=> $gid,
					":comic"	=> 0
			);
			foreach($secondLayer["CX"] as $comic) {
				$params[":comic"] = $comic;
				if($stmt->execute($params)) {
					$parent = $results->item("CX", $comic, $gid);

					foreach($stmt->fetchAll() as $row) {
						$results->addTag($row["mem_tag"], $gid, false, false, $parent);
					}
				}
			}
		}

		$this->stopTimer("second-layer");
	}
	protected function startTimer($code) {
		$this->_timer->start($code);
	}
	protected function stopTimer($code) {
		$this->_timer->stop($code);
	}
	protected function updateCollectionsIndex($showInfo, $spacer="") {
		$this->loadStatements();

		$stSubID = __CLASS__."::".__FUNCTION__;
		//
		// Statements
		// @{
		$query = "select  *\n";
		$query.= "from    {$this->_dbprefix}collections\n";
		$query.= "where   col_indexed = 'N'\n";
		$stmtColSel = $this->_db->prepare("{$stSubID}colsel", $query);

		$query = "update  {$this->_dbprefix}collections\n";
		$query.= "set     col_indexed = 'Y'\n";
		$query.= "where   col_id    = :id\n";
		$query.= " and    col_group = :group\n";
		$stmtColMrk = $this->_db->prepare("{$stSubID}colmrk", $query);

		$query = "insert ignore\n";
		$query.= "        into {$this->_dbprefix}search_items (sit_word, sit_order, sit_type, sit_group, sit_directory, sit_comic, sit_tag, sit_collection, sit_rule)\n";
		$query.= "        values (:id, :order, 'CL', :group, null, null, null, :collection, null)\n";
		$stmtColIns = $this->_db->prepare("{$stSubID}colins", $query);

		$query = "delete from     {$this->_dbprefix}search_items\n";
		$query.= "where           sit_collection = :id\n";
		$query.= " and            sit_group      = :group\n";
		$stmtColClr = $this->_db->prepare("{$stSubID}colclr", $query);
		// @}

		//
		// Loading Comics
		$stmtColSel->execute();
		foreach($stmtColSel->fetchAll() as $row) {
			$stmtColClr->execute(array(
					":id"		=> $row["col_id"],
					":group"	=> $row["col_group"]
			));

			$words = WCSearchFactory::StringToPatterns($row["col_name"]);

			if($showInfo) {
				echo "{$spacer}Adding collection '{$row["col_name"]}' ({$row["col_id"]})\n";
			}
			$order = 1;
			foreach($words as $order => $word) {
				if(!isset($this->_loadedWords[$word])) {
					$param = array(":word" => $word);
					$this->_stmtIndexInsert->execute($param);
					$this->_stmtIndexSelect->execute($param);
					$row2 = $this->_stmtIndexSelect->fetch();
					$id = $row2["six_id"];
					$this->_loadedWords[$word] = $id;
				}

				$stmtColIns->execute(array(
						":id"		=> $this->_loadedWords[$word],
						":order"	=> $order + 1,
						":group"	=> $row["col_group"],
						":collection"	=> $row["col_id"]));
			}

			$stmtColMrk->execute(array(
					":id"		=> $row["col_id"],
					":group"	=> $row["col_group"]
			));
		}
	}
	protected function updateComicsIndex($showInfo, $spacer="") {
		$this->loadStatements();

		$stSubID = __CLASS__."::".__FUNCTION__;
		//
		// Statements
		// @{
		$query = "select  *\n";
		$query.= "from    {$this->_dbprefix}comics\n";
		$query.= "where   cmc_lost    = 'N'\n";
		$query.= " and    cmc_indexed = 'N'\n";
		$stmtCmcSel = $this->_db->prepare("{$stSubID}cmcsel", $query);

		$query = "update  {$this->_dbprefix}comics\n";
		$query.= "set     cmc_indexed = 'Y'\n";
		$query.= "where   cmc_id    = :id\n";
		$query.= " and    cmc_group = :group\n";
		$stmtCmcMrk = $this->_db->prepare("{$stSubID}cmcmrk", $query);

		$query = "insert ignore\n";
		$query.= "        into {$this->_dbprefix}search_items (sit_word, sit_order, sit_type, sit_group, sit_directory, sit_comic, sit_tag, sit_collection, sit_rule)\n";
		$query.= "        values (:id, :order, 'CX', :group, null, :comic, null, null, null)\n";
		$stmtCmcIns = $this->_db->prepare("{$stSubID}cmcins", $query);

		$query = "delete from     {$this->_dbprefix}search_items\n";
		$query.= "where           sit_comic = :id\n";
		$query.= " and            sit_group = :group\n";
		$stmtCmcClr = $this->_db->prepare("{$stSubID}cmcclr", $query);
		// @}

		//
		// Loading Comics
		$stmtCmcSel->execute();
		foreach($stmtCmcSel->fetchAll() as $row) {
			$stmtCmcClr->execute(array(
					":id"		=> $row["cmc_id"],
					":group"	=> $row["cmc_group"]
			));

			$words = WCSearchFactory::StringToPatterns($row["cmc_next_name"] ? $row["cmc_next_name"] : $row["cmc_name"]);

			if($showInfo) {
				echo "{$spacer}Adding comic '{$row["cmc_name"]}' ({$row["cmc_id"]}@{$row["cmc_directory"]})\n";
			}
			$order = 1;
			foreach($words as $order => $word) {
				if(!isset($this->_loadedWords[$word])) {
					$param = array(":word" => $word);
					$this->_stmtIndexInsert->execute($param);
					$this->_stmtIndexSelect->execute($param);
					$row2 = $this->_stmtIndexSelect->fetch();
					$id = $row2["six_id"];
					$this->_loadedWords[$word] = $id;
				}

				$stmtCmcIns->execute(array(
						":id"		=> $this->_loadedWords[$word],
						":order"	=> $order + 1,
						":group"	=> $row["cmc_group"],
						":comic"	=> $row["cmc_id"]));
			}

			$stmtCmcMrk->execute(array(
					":id"		=> $row["cmc_id"],
					":group"	=> $row["cmc_group"]
			));
		}
	}
	protected function updateDirectoriesIndex($showInfo, $spacer="") {
		$this->loadStatements();

		$stSubID = __CLASS__."::".__FUNCTION__;
		//
		// Statements
		// @{
		$query = "select  *\n";
		$query.= "from    {$this->_dbprefix}directories\n";
		$query.= "where   dir_indexed = 'N'\n";
		$stmtDirSel = $this->_db->prepare("{$stSubID}dirsel", $query);

		$query = "update  {$this->_dbprefix}directories\n";
		$query.= "set     dir_indexed = 'Y'\n";
		$query.= "where   dir_id    = :id\n";
		$query.= " and    dir_group = :group\n";
		$stmtDirMrk = $this->_db->prepare("{$stSubID}dirmrk", $query);

		$query = "insert ignore\n";
		$query.= "        into {$this->_dbprefix}search_items (sit_word, sit_order, sit_type, sit_group, sit_directory, sit_comic, sit_tag, sit_collection, sit_rule)\n";
		$query.= "        values (:id, :order, 'DI', :group, :directory, null, null, null, null)\n";
		$stmtDirIns = $this->_db->prepare("{$stSubID}dirins", $query);

		$query = "delete from     {$this->_dbprefix}search_items\n";
		$query.= "where           sit_directory = :id\n";
		$query.= " and            sit_group     = :group\n";
		$stmtDirClr = $this->_db->prepare("{$stSubID}dirclr", $query);
		// @}

		//
		// Loading Comics
		$stmtDirSel->execute();
		foreach($stmtDirSel->fetchAll() as $row) {
			$stmtDirClr->execute(array(
					":id"		=> $row["dir_id"],
					":group"	=> $row["dir_group"]
			));

			$words = WCSearchFactory::StringToPatterns($row["dir_next_name"] ? $row["dir_next_name"] : $row["dir_name"]);

			if($showInfo) {
				echo "{$spacer}Adding directory '{$row["dir_name"]}' ({$row["dir_id"]})\n";
			}
			$order = 1;
			foreach($words as $order => $word) {
				if(!isset($this->_loadedWords[$word])) {
					$param = array(":word" => $word);
					$this->_stmtIndexInsert->execute($param);
					$this->_stmtIndexSelect->execute($param);
					$row2 = $this->_stmtIndexSelect->fetch();
					$id = $row2["six_id"];
					$this->_loadedWords[$word] = $id;
				}

				$stmtDirIns->execute(array(
						":id"		=> $this->_loadedWords[$word],
						":order"	=> $order + 1,
						":group"	=> $row["dir_group"],
						":directory"	=> $row["dir_id"]));
			}

			$stmtDirMrk->execute(array(
					":id"		=> $row["dir_id"],
					":group"	=> $row["dir_group"]
			));
		}
	}
	protected function updateRulesIndex($showInfo, $spacer="") {
		$this->loadStatements();

		$stSubID = __CLASS__."::".__FUNCTION__;
		//
		// Statements
		// @{
		$query = "select  *\n";
		$query.= "from    {$this->_dbprefix}rules\n";
		$query.= "where   rul_indexed = 'N'\n";
		$stmtRuleSel = $this->_db->prepare("{$stSubID}rulsel", $query);
		$query = "select  *\n";
		$query.= "from    {$this->_dbprefix}rule_actions\n";
		$query.= "where   rel_ra_indexed = 'N'\n";
		$stmtRActSel = $this->_db->prepare("{$stSubID}ractsel", $query);

		$query = "insert ignore\n";
		$query.= "        into {$this->_dbprefix}search_items (sit_word, sit_order, sit_type, sit_group, sit_directory, sit_comic, sit_tag, sit_collection, sit_rule)\n";
		$query.= "        values (:id, :order, 'RL', :group, null, null, null, null, :rule)\n";
		$stmtRuleIns = $this->_db->prepare("{$stSubID}rulins", $query);

		$query = "delete from     {$this->_dbprefix}search_items\n";
		$query.= "where           sit_rule  = :id\n";
		$query.= " and            sit_group = :group\n";
		$stmtRuleClr = $this->_db->prepare("{$stSubID}rulclr", $query);
		// @}

		//
		// Loading Rules
		$stmtRuleSel->execute();
		$stmtRActSel->execute();
		if($stmtRuleSel->rowCount() > 0 || $stmtRActSel->rowCount() > 0) {
			global	$wcGroupsHolder;
			global	$wcRulesHolder;
			foreach($wcGroupsHolder->groups() as $group) {
				foreach($wcRulesHolder->items($group) as $rule) {
					if($rule->indexed()) {
						continue;
					}

					$stmtRuleClr->execute(array(
							":id"		=> $rule->id,
							":group"	=> $group->id
					));

					$words = $rule->words();

					if($showInfo) {
						echo "{$spacer}Adding rule '{$rule->name}' ({$rule->id})\n";
					}
					$order = 1;
					foreach($words as $order => $word) {
						if(!isset($this->_loadedWords[$word])) { ///@todo ver si entra en estos IF
							$param = array(":word" => $word);
							$this->_stmtIndexInsert->execute($param);
							$this->_stmtIndexSelect->execute($param);
							$row2 = $this->_stmtIndexSelect->fetch();
							$id = $row2["six_id"];
							$this->_loadedWords[$word] = $id;
						}

						$stmtRuleIns->execute(array(
								":id"		=> $this->_loadedWords[$word],
								":order"	=> $order + 1,
								":group"	=> $group->id,
								":rule"		=> $rule->id));
					}

					$rule->indexed(true);
				}
			}
		}
	}
	protected function updateTagsIndex($showInfo, $spacer="") {
		$this->loadStatements();

		$stSubID = __CLASS__."::".__FUNCTION__;
		//
		// Statements
		// @{
		$query = "select  *\n";
		$query.= "from    {$this->_dbprefix}tags\n";
		$query.= "where   tag_indexed = 'N'\n";
		$stmtTagSel = $this->_db->prepare("{$stSubID}tagsel", $query);

		$query = "update  {$this->_dbprefix}tags\n";
		$query.= "set     tag_indexed = 'Y'\n";
		$query.= "where   tag_id    = :id\n";
		$query.= " and    tag_group = :group\n";
		$stmtTagMrk = $this->_db->prepare("{$stSubID}tagmrk", $query);

		$query = "insert ignore\n";
		$query.= "        into {$this->_dbprefix}search_items (sit_word, sit_order, sit_type, sit_group, sit_directory, sit_comic, sit_tag, sit_collection, sit_rule)\n";
		$query.= "        values (:id, :order, 'TA', :group, null, null, :tag, null, null)\n";
		$stmtTagIns = $this->_db->prepare("{$stSubID}tagins", $query);

		$query = "delete from     {$this->_dbprefix}search_items\n";
		$query.= "where           sit_tag   = :id\n";
		$query.= " and            sit_group = :group\n";
		$stmtTagClr = $this->_db->prepare("{$stSubID}tagclr", $query);
		// @}

		//
		// Loading Tags
		$stmtTagSel->execute();
		foreach($stmtTagSel->fetchAll() as $row) {
			$stmtTagClr->execute(array(
					":id"		=> $row["tag_id"],
					":group"	=> $row["tag_group"]
			));

			$words = WCSearchFactory::StringToPatterns($row["tag_name"]);

			if($showInfo) {
				echo "{$spacer}Adding tag '{$row["tag_name"]}' ({$row["tag_id"]})\n";
			}
			$order = 1;
			foreach($words as $order => $word) {
				if(!isset($this->_loadedWords[$word])) {
					$param = array(":word" => $word);
					$this->_stmtIndexInsert->execute($param);
					$this->_stmtIndexSelect->execute($param);
					$row2 = $this->_stmtIndexSelect->fetch();
					$id = $row2["six_id"];
					$this->_loadedWords[$word] = $id;
				}

				$stmtTagIns->execute(array(
						":id"		=> $this->_loadedWords[$word],
						":order"	=> $order + 1,
						":group"	=> $row["tag_group"],
						":tag"		=> $row["tag_id"]));
			}

			$stmtTagMrk->execute(array(
					":id"		=> $row["tag_id"],
					":group"	=> $row["tag_group"]
			));
		}
	}

	//
	// Public class methods.
	/**
	* @return WCSearch Returns the singleton instance of this class.
	*/
	public static function Instance() {
		if (!isset(self::$_Instance)) {
			$c = __CLASS__;
			self::$_Instance = new $c;
		}

		return self::$_Instance;
	}
}
?>
